--[[
Support routines for luagame, extracted from libluartk project
wangdm001@163.com, 2025-8
]]

local dbg_flag
local ffi = require("ffi")
local is_windows = jit.os == "Windows"

if is_windows then
	ffi.cdef([[
	int GetConsoleOutputCP();
	int _isatty(int);
	int _fileno(struct FILE *);
	]])
	console_cp = ffi.C.GetConsoleOutputCP()
	stderr_is_a_tty = ffi.C._isatty(ffi.C._fileno(io.stderr)) ~= 0
	stdout_is_a_tty = ffi.C._isatty(ffi.C._fileno(io.stdout)) ~= 0
	if stderr_is_a_tty and console_cp ~= 65001 then
		stderr_filter = function(s)
			return iconv(65001, console_cp, s)
		end
	end
	if stdout_is_a_tty and console_cp ~= 65001 then
		stdout_filter = function(s)
			return iconv(65001, console_cp, s)
		end
	end
end

stderr_filter = stderr_filter or function(s)
	return s
end
stdout_filter = stdout_filter or function(s)
	return s
end

function dbg_print(fmt, ...)
	if not dbg_flag then
		return
	end
	printk(2, fmt, ...)
end

if getprop then
	-- already in wmclua
	dbg_flag = getpropFlag("debug")
else
	local props = {}
	function getprop(name)
		return props[name]
	end

	function getprops()
		return props
	end

	function getpropFlag(name)
		local flag = getprop(name)
		if not flag then
			return false
		end
		return tonumber(flag) ~= 0
	end
end

function table_each(tbl, func)
	for k, v in pairs(tbl) do
		func(k, v)
	end
end

function table_ieach(tbl, func)
	for k, v in ipairs(tbl) do
		func(k, v)
	end
end

printf = function(fmt, ...)
	local str = string.format(fmt, ...)
	str = stdout_filter(str)
	print(str)
end

printk = function(level, fmt, ...)
	local msg = string.format(fmt, ...)
	if level == 2 then
		msg = stderr_filter(msg)
		io.stderr:write(msg)
	elseif level == 1 then
		msg = stdout_filter(msg)
		io.write(msg)
	elseif level == 0 and dbg_flag then
		msg = stdout_filter(msg)
		io.write(msg)
	end
end

function findFiles(root, patt, recursive)
	local f
	local ans = {}
	local lfs = require("lfs")
	if lfs.attributes(root, "mode") ~= "directory" then
		return {}
	end
	for f in lfs.dir(root) do
		local fpath = makePath(root, f)
		if lfs.attributes(fpath, "mode") == "directory" then
			if f ~= "." and f ~= ".." and recursive then
				local sub = findFiles(fpath, patt, recursive)
				local _, v
				for _, v in ipairs(sub) do
					table.insert(ans, v)
				end
			end
		else
			if
				not patt
				or (type(patt) == "function" and patt(fpath, f))
				or (type(patt) == "string" and string.match(f, patt))
			then
				table.insert(ans, fpath)
			end
		end
	end
	return ans
end

function iconv(from, to, str)
	return str
end

function fileExists(f)
	local f = io.open(f, "r")
	if f then
		f:close()
		return true
	end
end

local function tableToLuaCodeInternal(tbl, indent, is_inline, recursive_preventor)
	indent = indent or 0
	local is_compact = (indent == nil) or is_inline
	local newline = (is_compact and "") or "\n"
	local space = is_compact and "" or string.rep("  ", indent)
	local inner_space = is_compact and "" or string.rep("  ", indent + 1)
	local result = { "{" }
	local hasMapPart

	if recursive_preventor[tbl] then
		return "<recursive>:" .. tostring(tbl)
	end
	recursive_preventor[tbl] = 1
	-- 对数组部分和非数组部分分别处理
	local array_part = {}
	local map_part = {}

	-- 分离数组部分和map部分
	local max_index = 0
	for k, _ in pairs(tbl) do
		if type(k) == "number" and k > max_index then
			max_index = k
		end
	end

	-- 收集数组部分（连续数字键）
	for i = 1, max_index do
		if tbl[i] ~= nil then
			table.insert(array_part, { key = i, value = tbl[i] })
		end
	end

	-- 收集非数组部分
	for k, v in pairs(tbl) do
		if type(k) ~= "number" or k < 1 or k > max_index or math.floor(k) ~= k then
			table.insert(map_part, { key = k, value = v })
		end
	end
	hasMapPart = #map_part > 0

	-- 先处理数组部分
	for _, item in ipairs(array_part) do
		local value_str
		if type(item.value) == "table" then
			value_str = tableToLuaCodeInternal(item.value, indent + 1, is_compact, recursive_preventor)
		elseif type(item.value) == "string" then
			value_str = string.format("%q", item.value)
		elseif type(item.value) == "boolean" or item.value == nil then
			value_str = tostring(item.value)
		else
			value_str = tostring(item.value)
		end
		if hasMapPart then
			table.insert(result, inner_space .. "[" .. item.key .. "] = " .. value_str .. ",")
		else
			table.insert(result, inner_space .. value_str .. ",")
		end
	end

	-- 再处理map部分
	for _, item in ipairs(map_part) do
		local key_str
		if type(item.key) == "string" and string.match(item.key, "^[a-zA-Z_][a-zA-Z0-9_]*$") then
			key_str = item.key
		else
			key_str = '["' .. tostring(item.key) .. '"]'
		end

		local value_str
		if type(item.value) == "table" then
			value_str = tableToLuaCodeInternal(item.value, indent + 1, is_compact, recursive_preventor)
		elseif type(item.value) == "string" then
			value_str = string.format("%q", item.value)
		elseif type(item.value) == "boolean" or item.value == nil then
			value_str = tostring(item.value)
		else
			value_str = tostring(item.value)
		end
		table.insert(result, inner_space .. key_str .. " = " .. value_str .. ",")
	end

	table.insert(result, space .. "}")
	recursive_preventor[tbl] = nil
	return table.concat(result, newline)
end

function tableToLuaCode(tbl, indent, is_inline)
	local recursive_preventor = {}
	return tableToLuaCodeInternal(tbl, indent, is_inline, recursive_preventor)
end

function tprint(...)
	local values = {}
	table_ieach({ ... }, function(_, v)
		local tp = type(v)
		if tp == "table" then
			table.insert(values, tableToLuaCode(v))
		else
			table.insert(values, tostring(v))
		end
	end)
	print(table.concat(values, "\t"))
end

local function escape_xml(str)
	return str:gsub("[&<>\"']", {
		["&"] = "&amp;",
		["<"] = "&lt;",
		[">"] = "&gt;",
		['"'] = "&quot;",
		["'"] = "&apos;",
	})
end
-- 将 Lua 表转换为 XML 字符串的函数
local function tableToXmlInternal(t, indent, recursive_preventor)
	assert(not recursive_preventor[t], "Recursive table")
	recursive_preventor[t] = true
	indent = indent or ""
	local xml = ""
	for k, v in pairs(t) do
		if type(k) == "number" then
			k = "item" .. k
		end
		if type(v) == "table" then
			local attr = { " " }
			local v2 = {}
			table_each(v, function(sk, sv)
				if type(sk) == "number" or type(sv) == "table" then
					v2[sk] = sv
					return
				end
				table.insert(attr, string.format('%s="%s"', sk, escape_xml(tostring(sv))))
			end)
			attr = table.concat(attr, " ")
			xml = xml .. indent .. "<" .. k .. attr .. ">\n"
			xml = xml .. tableToXmlInternal(v2, indent .. "	", recursive_preventor)
			xml = xml .. indent .. "</" .. k .. ">\n"
		else
			xml = xml .. indent .. "<" .. k .. ">" .. escape_xml(tostring(v)) .. "</" .. k .. ">\n"
		end
	end
	recursive_preventor[t] = nil
	return xml
end

function tableToXml(t, indent)
	local recursive_preventor = {}
	return tableToXmlInternal(t, indent, recursive_preventor)
end

if is_windows then
	-- 定义 Windows 高精度计时 API
	ffi.cdef([[
		typedef long long int64_t;
		int QueryPerformanceCounter(int64_t* lpPerformanceCount);
		int QueryPerformanceFrequency(int64_t* lpFrequency);
	]])

	local time_utils = {}

	-- 初始化计时器频率
	local freq = ffi.new("int64_t[1]")
	ffi.C.QueryPerformanceFrequency(freq)
	time_utils.freq = tonumber(freq[0])

	-- 获取当前高精度时间（秒）
	function time_mark()
		local counter = ffi.new("int64_t[1]")
		ffi.C.QueryPerformanceCounter(counter)
		return tonumber(counter[0]) / time_utils.freq
	end
else
	ffi.cdef([[
		typedef long time_t;
		typedef struct timeval {
			time_t tv_sec;
			time_t tv_usec;
		} timeval;
		int gettimeofday(struct timeval* tv, void* tz);
	]])
	function time_mark()
		local tv = ffi.new("timeval")
		ffi.C.gettimeofday(tv, nil)
		return tonumber(tv.tv_sec + tv.tv_usec / 1e6)
	end
end

local function init_args()
	arg_save = arg
	arg = {}
	for i, v in pairs(arg_save) do
		if i <= 0 then
			arg[i] = v
		else
			local fname, fval = v:match("(.*)=(.*)")
			if fname and fval then
				props[fname] = fval
			else
				table.insert(arg, v)
			end
		end
	end
end

init_args()
dbg_flag = getpropFlag("debug")
